package org.song.aspectJ;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.song.cache.PowerEqualsCache;
import org.song.exception.myException.CommonException;
import org.song.model.threadlocal.WebContextUtil;
import org.song.util.FunctionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * 接口幂等切面处理
 * 幂等概念：多次运算，结果相同。用户/服务多次调用，不会对实际业务造成影响就是幂等性。查询语句天生具有幂等性质、删除语句不考虑返回值也是具有幂等性的。
 * 保证幂等方法：客户端保证幂等，使用loading禁止用户重复请求。服务端：数据库表加唯一约束索引来保证幂等性、缓存加幂等锁，设置过期时间。保证在指定之间内指定用户的指定API不能重复操作。
 */
@Slf4j
@Aspect
@Component
public class PowerEqualsAspect {

    // 获取幂等性注解横切关注点
    @Pointcut(value = "@annotation(org.song.annotation.PowerEquals)")
    public void powerEqualsPointCut() {

    }

    @Autowired(required = true)
    @Qualifier(value = "powerEqualsCacheImpl")
    private PowerEqualsCache powerEqualsCache;
/*
    @Autowired( required = true )
    @Qualifier( value = "userUtil")
    private UserUtil userUtil;*/

    /**
     * 服务端接口幂等处理
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around(value = "powerEqualsPointCut()")
    public Object powerEqualsAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 用户级别隔离，获取当前用户Id，为空则获取当前客户端IP
        // UserUtil.getOnlineUser();
        final HttpServletRequest currentRequest = WebContextUtil.getCurrentRequest();
        // 客户端IP + uri 保证互斥性 指定客户端指定不能重复请求
        final String clientKey = FunctionUtil.getIpAddress(currentRequest) + ":" + currentRequest.getRequestURI();
        // 加幂等锁 ， 隔离级别为客户端级别 无需自旋 失败则为重复请求
        if (!powerEqualsCache.setPowerEqualsLock(clientKey, clientKey)) {
            // 加锁失败重复提交
            throw new CommonException("请不要重复操作~");
        }
        //TODO: 护线程进行锁延时
        log.info(  clientKey + " ： 幂等加锁成功，启动守护线程进行锁延时");
        // 执行目标方法
        Object proceed = proceedingJoinPoint.proceed();
        // 目标方法执行结束 ， 释放幂等锁 ， 发生异常等锁自动失效
        powerEqualsCache.deletePowerEqualsLock(clientKey);
        return proceed;
    }

}
